#!/usr/bin/perl -w

package perlmod::camera2;
#perl module for gphoto2 communication

#thread support
use threads;
use threads::shared;

#queue support
use Thread::Queue;

#ipc bi-directional open support
use IPC::Open2;




#create a camera object
#my $camera = perlmod::camera2->new();
sub new {
	
	return bless({});
	
}


#begin background thread
#my $result = $camera->begin();
#$result 1 on success, undef on failure
sub begin {
	
	#get our passed data
	my ( $self , $class ) = @_;
	
	#create my file handles
	my $inputfh = undef;
	my $outputfh = undef;
	
	#add the shell command
	$shellcommand = "sudo gphoto2 --force-overwrite --shell 2>&1";
	
	#add my gphoto pid object
	$gphotopid = open2( $outputfh , $inputfh , $shellcommand );
	
	#check if we opened correctly
	if ( !defined( $gphotopid ) ) {
		
		warn "Error: Could not start gphoto2 correctly.\n";
		
		return undef;
		
	}
	
	
	#we have opened gphoto2 so now lets add it to our self object
	#$self->{'gphoto2ipc'} = $ipc;
	
	$self->{'gphotoipc'} = {
								'inputfh' => $inputfh,
								'outputfh' => $outputfh,
								'pid' => $gphotopid,
								'shellcommand' => $shellcommand
							};
	
	
	#now set up a blockable thread
	
	#create some storage
	my $threadipc = {};
	
	#stash a reference in our self early
	$self->{'threadipc'} = $threadipc;
	
	#create two queues for the thread
	$threadipc->{'inputqueue'} = Thread::Queue->new();
	$threadipc->{'outputqueue'} = Thread::Queue->new();
	
	#spin off a thread
	$threadipc->{'pid'} = async { backgroundthread($self,$class); };
	
	#check it's running
	if ( !defined( $threadipc->{'pid'} ) ) {
		
		warn "Could not start background thread\n";
		
		return undef;
		
	}
	
	#now leave it up to the background thread from here
	
	#end of $camera->begin()
	$self->{'fhconnected'} = 1;
	
	return 1;
}




#wait on camera status
#my $status = $camera->status();
#returns true (1) if busy or false (0) on idle
sub status {
	
	my ( $self , $class ) = @_;
	
	return $self->{'threadipc'}->{'pid'}->is_joinable();
	
}

#close camera connection
#my $return = $camera->finish();
#returns true (1) on success or false (0) on fail
sub finish {
	
	my ( $self , $class ) = @_;
	
	if ( $self->{'threadipc'}->{'pid'}->is_joinable() ) {
		
		$self->{'threadipc'}->{'pid'}->join();
		
	}
	
	return 1;
}

#all done!


sub backgroundthread {
	
	#get my passed parameters
	my ( $self , $class ) = @_;
	
	#grab my output filehandle
	my $outputfh = $self->{'gphotoipc'}->{'outputfh'};
	
	#create some buffers
	my $charbuf = '';
	my $linebuf = '';
	my $readresult;
	
	#create a flag for the loop
	my $mainloopexit = 0;
	
	#now loop on it
	while ( $mainloopexit == 0 ) {
		
		#reset out charbuf
		$charbuf = '';
		
		#read a byte from the outputfh if available
		$readresult = read($outputfh,$charbuf,1);
		
		#check if we got anything
		if ( !defined($readresult) ) {
			
			#undef from a read?  something went wrong.
			print "mainloop read from outputfh returned undef\n";
			$self->{'fhconnected'} = 0;
			
			#trigger exit from main loop
			$mainloopexit = 1;
			
			
			#and jump to it early
			next;
			
		}
		
		#we definitely got *something*
		if ( $readresult == 0 ) {
			
			#*sigh* zero result on read
			print "mainloop read from outputfh returned zero\n";
			$self->{'fhconnected'} = 0;
			
			print $self->{'fhconnected'} . "\n";
			
			#trigger exit from main loop
			$mainloopexit = 1;
			
			
			#and jump to it early
			next;
			
		}
		
		#yay we have a byte
		
		#add it to the line buffer
		$linebuf .= $charbuf;
		
		#check if it is a completed response
		if ( $charbuf eq "\n" ) {
			
			#check it is not a blank line
			if ( $linebuf eq "\n" ) {
				
				#clear the buffers
				$linebuf = '';
				$charbuf = '';
				
				#and loop return early
				next;
				
			}
			
			#parse the response
			my $result = parseresponse($self,$class,$linebuf);
			
			#clear the buffers
			$linebuf = '';
			$charbuf = '';
			
			#loop return early
			next;
			
		}
		
		#check if it is a command prompt
		#do a quick check first
		if ( ( $linebuf ) &&
		( length( $linebuf ) > 10 ) &&
		(substr( $linebuf , 0, 10 ) eq "gphoto2: {" ) ) {
			
			#it looks like a command prompt
			#make sure
			if ( $linebuf =~ /^gphoto2: \{.*?\} \/.*?> $/ ) {
				
				#parse the command prompt
				
				my $result = sendcommand($self,$class);
				
				#clear the buffers
				$linebuf = '';
				$charbuf = '';
				
				#loop return early
				next;
				
			}
			
		}
		
	#end of mainloop
	}
	
	
	#don't forget we are in a separate thread in this routine.
	#we need to exit properly or we will become a zombie!  arrrg!  brainz!
	threads->exit();
	
}



#response parsing
sub parseresponse {
	
	#get passed parameters
	my ( $self , $class , $linebuf ) = @_;
	
	
	#ignore wait events
	if ( $linebuf =~ /^wait-event [0-9]+s*/ ) {
		
		#safely ignore
		return 1;
		
	}
	
	#ignore response to wait events
	if ( $linebuf =~ /^Waiting for [0-9]+.*? events from camera/ ) {
		
		#safely ignore
		return 1;
		
	}
	
	#unknown ptp properties
	if ($linebuf =~ /^UNKNOWN PTP Property .... changed/ ) {
		
		#safely ignore
		return 1;
		
	}
	
	
	print "gphoto >: $linebuf";
	
	return 1;
	
}




#command sending
sub sendcommand {
	
	#bet my passed parameters
	my ( $self , $class ) = @_;
	
	#if we have any pending input
	if ( $self->{'threadipc'}->{'inputqueue'}->pending() ) {
		
		foreach $line ( @{ $self->{'threadipc'}->{'inputqueue'}->dequeue() } ) {
			
			print "gphoto <: $line";
			
			#send it
			print { $self->{'gphotoipc'}->{'inputfh'} } $line;
			
		}
		
	}
	
	#if we do not have anything pending
	
	#send a wait event
	print { $self->{'gphotoipc'}->{'inputfh'} } "wait-event 1\n";
	
	#and return great success
	return 1;
	
}










1;